﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Threading;
using System.Text.RegularExpressions;

namespace HtmlParserLib
{
    public enum ReportEventType
    {
        Infomation,        
        Error
    }

    public class ReportEventArgs : EventArgs
    {
        public ReportEventType ReportType
        {
            get;set;
        }
        
        public string Message
        {
            get;set;
        }

        public string Url
        {
            get;
            set;
        }

        public DateTime CrawledTime
        {
            get;
            set;
        }
    }

    public delegate void ReportEventHandler(object sender, ReportEventArgs e);

    public class Crawler
    {
        bool _isRuning = false;

        public event ReportEventHandler OnReport;

        private ConcurrentQueue<UrlConfigurations> _crawleTaskQueue=null;
        private ConcurrentQueue<WebCrawlingHistories> _writeToHistoryQueue = null;         

        private BackgroundWorker _readConfigWorker = null;
        private BackgroundWorker _writeHistoryWorker = null;

        private List<BackgroundWorker> _parserWorkers = null;

        public Crawler()
        {
            _crawleTaskQueue = new ConcurrentQueue<UrlConfigurations>();
            _writeToHistoryQueue = new ConcurrentQueue<WebCrawlingHistories>();            

            _readConfigWorker = new BackgroundWorker();
            _readConfigWorker.WorkerSupportsCancellation = true;
            _readConfigWorker.DoWork += ReadConfigWorker_DoWork;

            _writeHistoryWorker = new BackgroundWorker();
            _writeHistoryWorker.WorkerSupportsCancellation = true;
            _writeHistoryWorker.DoWork += WriteHistoryWorker_DoWork;

            _parserWorkers = new List<BackgroundWorker>();
            for (int i = 0; i < this.ParserThreads; i++)
            {
                var w = new BackgroundWorker();
                w.WorkerSupportsCancellation = true;
                w.DoWork += Parser_DoWork;
                _parserWorkers.Add(w);
            }        
        }

        /// <summary>
        /// 获取解析网页的线程数
        /// </summary>
        private int ParserThreads
        {
            get
            {                
                return 1;
            }
        }

        void Parser_DoWork(object sender, DoWorkEventArgs e)
        {            
            while (true)
            {
                if (_isRuning)
                {
                    this.DeQueueCrawlerTaskAndProcess();
                    Thread.Sleep(1000);
                }
                else
                {
                    Thread.Sleep(3000);                //未启时间隔一会再启动
                }  
            }
        }

        void WriteHistoryWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                if (_isRuning)
                {
                    this.DeQueueWriteHistoryTask();
                }
                else
                {
                    Thread.Sleep(3000);                //未启时间隔一会再启动
                }  
            }
        }

        void ReadConfigWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                if (_isRuning)
                {
                    this.EnQueueCrawlerTask();
                    Thread.Sleep(5000);                //间隔一段时间再读
                }
                else
                {
                    Thread.Sleep(3000);                //未启时间隔一会再启动
                }                
            }
        }

        protected virtual void EnQueueCrawlerTask()
        {
            try
            {
                var dbContext = new Entities();
                var lst = (from url in dbContext.UrlConfigurations.Include("Plugins") where url.Available == true select url).ToList();
                var now = DateTime.Now;
                foreach (var config in lst)
                {
                    UrlConfigurations obj = null;
                    if (config.LastUpdateTime.HasValue)
                    {
                        var lastTime = config.LastUpdateTime.Value;
                        switch (config.FrenquenceUnit)
                        {
                            case 1:
                                lastTime = lastTime.AddSeconds(config.Frequence);
                                break;
                            case 2:
                                lastTime = lastTime.AddMinutes(config.Frequence);
                                break;
                            case 3:
                                lastTime = lastTime.AddHours(config.Frequence);
                                break;
                            case 4:
                                lastTime = lastTime.AddDays(config.Frequence);
                                break;
                            default:
                                break;
                        }
                        if (lastTime <= now)
                        {
                            obj = config;
                        }
                    }
                    else
                    {
                        obj = config;
                    }
                    if (obj != null)
                    {
                        if (!_crawleTaskQueue.Contains(obj))
                        {
                            _crawleTaskQueue.Enqueue(obj);
                            obj.Status = 1;
                            dbContext.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
            
        }

        protected virtual void DeQueueCrawlerTaskAndProcess()
        {
            try
            {
                var dbContext = new Entities();
                UrlConfigurations config = null;
                var r = _crawleTaskQueue.TryDequeue(out config);
                if (config != null)
                {
                    config.Status = 2;
                    UrlConfigurations cloneConfig = config.Clone();
                    
                    dbContext.Entry<UrlConfigurations>(cloneConfig).State = System.Data.EntityState.Modified;
                    dbContext.SaveChanges();
                    
                    IHtmlParser parser = GetParser(config);
                    if (parser != null)
                    {
                        parser.ParsedLink += parser_ParsedLink;
                        var list = GetUriList(config.Url);
                        foreach (var item in list)
                        {
                            parser.ParseUrl(item.OriginalString);
                        }
                    }
                    else
                    {
                        if (OnReport != null)
                        {
                            OnReport(this, new ReportEventArgs()
                            {
                                ReportType = ReportEventType.Error,
                                Message = string.Format("未能解析{0}所对应的插件{1}", config.Name, config.Plugins.Name)
                            }
                            );
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        protected virtual void parser_ParsedLink(object sender, ParsedLinkEventArgs e)
        {
            string url = e.Url.Replace("&amp;", "&");
            if (OnReport != null)
            {
                OnReport(this, new ReportEventArgs()
                {
                    ReportType = ReportEventType.Infomation,
                    Message = "成功解析链接："+e.Title,
                    Url=url,
                    CrawledTime=DateTime.Now                    
                }
                );               
            }
            var parser = sender as IHtmlParser;
            var config = parser.ArgObject as UrlConfigurations;

            WebCrawlingHistories history = new WebCrawlingHistories();
            history.IsApproved = false;
            history.LastUpdateTime = DateTime.Now;
            history.Title = e.Title;
            history.Url = url;
            history.UrlConfigurations = config;
            history.OtherInfo = e.OtherInfo;
            _writeToHistoryQueue.Enqueue(history);
        }

        protected virtual void DeQueueWriteHistoryTask()
        {
            try
            {
                var dbContext = new Entities();
                WebCrawlingHistories history = null;
                var r = _writeToHistoryQueue.TryDequeue(out history);
                if (history != null)
                {
                    var flag = dbContext.WebCrawlingHistories.Any(t => t.Url.Equals(history.Url));
                    if (!flag)
                    {
                        history.UrlConfigurations.LastUpdateTime = history.LastUpdateTime;
                        history.UrlConfigurations.Status = 3;

                        WebCrawlingHistories newObj = history.Clone() as WebCrawlingHistories;

                        newObj.IsApproved = true;

                        dbContext.Entry<WebCrawlingHistories>(newObj).State = System.Data.EntityState.Added;
                        dbContext.Entry<UrlConfigurations>(newObj.UrlConfigurations).State = System.Data.EntityState.Modified;

                        dbContext.WebCrawlingHistories.Add(newObj);

                        dbContext.SaveChanges();
                    }
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void HandleException(Exception ex)
        {
            if (OnReport != null)
            {
                OnReport(this, new ReportEventArgs() { 
                    Message=ex.Message,
                    ReportType=ReportEventType.Error,
                    CrawledTime=DateTime.Now
                });
            }
        }

        public void Start()
        {
            _isRuning = true;
            if (!_readConfigWorker.IsBusy)
            {
                _readConfigWorker.RunWorkerAsync();
                foreach (var w in _parserWorkers)
                {
                    w.RunWorkerAsync();
                }

                _writeHistoryWorker.RunWorkerAsync();
            }
        }

        public void Stop()
        {
            _isRuning = false;
        }

        private IHtmlParser GetParser(UrlConfigurations config)
        {
            var o = Activator.CreateInstanceFrom(config.Plugins.AssemblyName, config.Plugins.TypeName);
            var p = o.Unwrap() as IHtmlParser;
            if (p != null)
            {
                p.ArgObject = config;
            }
            return p;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="urlConfig"></param>
        /// <returns></returns>
        /// <remarks>
        /// 支持的Url模式：
        /// 以,分隔的多个网址，以示为示例
        /// index.htm:只包含index.htm
        /// index(_1-2).htm：包括index_1.htm、index_2.htm
        /// index(1-2).htm：包括index1.htm、index2.htm
        /// index[_1-2].htm：包括index.htm、index_1.htm、index_2.htm
        /// index[1-2].htm:包括index.htm、index1.htm、index2.htm
        /// </remarks>
        private static List<Uri> GetUriList(string urlConfig1)
        {
            List<Uri> list = new List<Uri>();
            string[] urls = urlConfig1.Split(',');
            foreach (var url in urls)
            {
                if (string.IsNullOrEmpty(url))
                {
                    continue;
                }

                try
                {
                    string[] patterns = new string[] { @"\[\w+-\w+\]", @"\(\w+-\w+\)" };
                    string numPattern = @"\d+-\d+";
                    bool flag = false;
                    foreach (var pattern in patterns)
                    {
                        flag = false;
                        Regex regex = new Regex(pattern);
                        var mainMatch = regex.Match(url);
                        if (mainMatch.Success)
                        {
                            flag = true;
                            //找到前缀
                            var prefix = Regex.Replace(mainMatch.Value, numPattern, "");
                            prefix = prefix.Replace("(", "");
                            prefix = prefix.Replace(")", "");
                            prefix = prefix.Replace("[", "");
                            prefix = prefix.Replace("]", "");

                            var numMatch = Regex.Match(mainMatch.Value, numPattern);
                            string[] nums = numMatch.Value.Split('-');
                            int from = int.Parse(nums[0]);
                            int to = int.Parse(nums[1]);

                            for (int i = to; i >= from; i--)
                            {
                                var target = regex.Replace(url, prefix + i.ToString());
                                list.Add(new Uri(target));
                            }

                            if (pattern == patterns[0])                     //如果是[]的则还取去掉括号的网址，例如：index[1-20].htm,则取index.htm,index1.htm,index2.htm.....
                            {
                                list.Add(new Uri(regex.Replace(url, "")));
                            }

                            break;
                        }
                    }
                    if (!flag)
                    {
                        list.Add(new Uri(url));
                    }
                }
                catch
                {
                    list.Add(new Uri(url));
                }
                finally
                {
                    if (list.Count == 0)
                    {
                        list.Add(new Uri(url));
                    }
                }
            }

            return list;
        }
    }
}
